package server;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Vector;

import javax.microedition.io.Connector;

import requestEnvelopes.agent.ReceiveEvent;
import server.interfaces.IMobileAgentExecutionEngine;
import server.interfaces.IMobileAgentMigrationManager;

import com.CharNotValidException;
import com.Constants;
import com.Event;
import com.LedsManager;
import com.ParamsLabel;
import com.sun.spot.interisolate.ReplyEnvelope;
import com.sun.spot.interisolate.RequestSender;
import com.sun.spot.io.j2me.radiostream.RadiostreamConnection;
import com.sun.spot.resources.transducers.LEDColor;
import com.sun.squawk.Isolate;

public class MobileAgentMigrationManager implements IMobileAgentMigrationManager {
	
	
	
	private IMobileAgentExecutionEngine executionEngine;
	private Vector 	migrationAddresses;
	private Vector 	migrationAgentNames;
	private boolean inMigration;
	private String	inMigrationAddress;
	private String 	inMigrationAgent;

	
	/**
	 * Creates a MobileAgentMigrationManager 
	 * @param executionEngine ExecutionEngine's reference
	 */
	public MobileAgentMigrationManager(IMobileAgentExecutionEngine executionEngine) {
		
		this.executionEngine 	= executionEngine;
		this.migrationAddresses	= new Vector();
		this.migrationAgentNames= new Vector();
		this.inMigration		= false;
		this.inMigrationAddress = "";
		this.inMigrationAgent	= "";

	}
	
	/**
	 * Migration from this server
	 */
	public synchronized void addAgentToMigrate(String agentName, String nodeID) {
		
		this.migrationAgentNames.addElement(agentName);
		this.migrationAddresses.addElement(nodeID);
		this.notifyAll();

	}
	
	
	public void migrationAckReceived(String address, String agent) {
					
		Isolate isolate = this.executionEngine.getAgent(agent);			
		this.migrateAgentTo(isolate, address);
		
		try {
			isolate.unhibernate();
			this.executionEngine.terminateAgent(agent);
			
			
		}
		catch (RuntimeException e) {
			LedsManager.error(LEDColor.GREEN);
			e.printStackTrace();
		}
	}
	
	/**
	 * Opens a streamConnection to send the Agent's Isolate
	 * @param isolate Agent's Isolate
	 * @param nodeID destination address
	 */
	private void migrateAgentTo(Isolate isolate, String nodeID) {
		RadiostreamConnection conn = null;
		OutputStream dos = null;
		try {			
			conn = (RadiostreamConnection) Connector.open("radiostream://"+nodeID+":" + Constants.MIGRATION_PORT); 
			dos = conn.openOutputStream();
		   if (!isolate.isHibernated()) {
				isolate.hibernate();
		        isolate.join(); 
		   }
	        if (isolate.isHibernated()) {  
	        	
	        	isolate.save(new DataOutputStream(dos), "migration");
	        	
	        	LedsManager.radioTransmission(false,false);
	        	
	        }
		}
		catch(IOException ioe) {
			LedsManager.error(LEDColor.WHITE);
			ioe.printStackTrace();
		}
		catch(IllegalStateException ise) {
			LedsManager.error(LEDColor.WHITE);
			ise.printStackTrace();
		}		
		catch(Exception e) {			
			LedsManager.error(LEDColor.WHITE);
			e.printStackTrace();
		}
		finally {			
			try {
				dos.close();	         
		        conn.close();	
			}
			catch (IOException e) {				
				LedsManager.error(LEDColor.WHITE);
				e.printStackTrace();
			}
		}

	}
	
	/**
	 * Sends an Ack to the departure node
	 */
	private void sendAck() {
		try {
			/*
			 * Acknowledge Migration
			 */
			Event ackMigration = new Event(Event.MGR_ACK, Event.NOW);
			/*
			 * Address
			 */
			ackMigration.setParam(ParamsLabel.EVT_ADDRESS, this.inMigrationAddress);
			/*
			 * AgentToMigrate Name
			 */
			ackMigration.setParam(ParamsLabel.AGT_NAME, this.inMigrationAgent);
			this.executionEngine.getCommunicationChannel().send(ackMigration);
		}
		catch (CharNotValidException e) {
			LedsManager.error(LEDColor.WHITE);
			e.printStackTrace();
		}
	}
	
	/**
	 * Loads and unhibernate the Isolate, restarting it.
	 */
	private void receiveAgent() {

		try {
			RadiostreamConnection conn = (RadiostreamConnection) Connector.open("radiostream://" + this.inMigrationAddress + ":" + Constants.MIGRATION_PORT); 
   
			InputStream dis = conn.openInputStream();
			
			Isolate isolate = Isolate.load(new DataInputStream(dis), "migration");             
			LedsManager.radioTransmission(false,true);

			isolate.unhibernate();
			
			String agentName = isolate.getMainClassArguments()[0];
			
			this.executionEngine.removeAgentProxy(agentName);
			
			executionEngine.addAgent(isolate);
			
			RequestSender agent	= RequestSender.lookup(agentName);
	    	Event eventMigationExecutedAgent = new Event(this.executionEngine.getExecutionEngineName(), agentName, 
		    										Event.MGR_EXECUTED, Event.NOW);
	    	ReplyEnvelope reply = agent.send(new ReceiveEvent(eventMigationExecutedAgent));
		    reply.checkForRuntimeException();
			
		    try {
				Event eventMigationExecutedServer = new Event(this.executionEngine.getExecutionEngineName(), this.executionEngine.getExecutionEngineName(),
						Event.MGR_EXECUTED, Event.NOW);
				eventMigationExecutedServer.setParam(ParamsLabel.EVT_ADDRESS, this.inMigrationAddress);
				eventMigationExecutedServer.setParam(ParamsLabel.AGT_NAME, agentName);
				this.executionEngine.getCommunicationChannel().send(eventMigationExecutedServer);
			}
			catch (CharNotValidException e) {
				LedsManager.error(LEDColor.WHITE);
				e.printStackTrace();
			}
			dis.close();
			conn.close();
			/**
			 * Reset Variables
			 */
			this.inMigration 		= false;
			this.inMigrationAddress = "";
			this.inMigrationAgent	= "";
		}
		catch (IOException e) {
			LedsManager.error(LEDColor.WHITE);
			e.printStackTrace();
		}
	}
	
	/**
	 * Receives a migration request
	 * @param nodeID destination node
	 * @param agent agent to migrate
	 */
	public synchronized void migrationRequest(String nodeID, String agent) {
		
		/*
		 *  Se esiste una immigrazione in sospeso scarto le ulteriori richieste
		 */
		if (!this.inMigration) {
			this.inMigration 		= true;
			this.inMigrationAddress = nodeID;
			this.inMigrationAgent	= agent;
			this.notifyAll();
		}
	}
	
	public void run() {
		
		while(true){
			waitForAgentsToMigrate();
			try {

				if (this.inMigration) {
					sendAck();
					receiveAgent();
				} 
				else {
					
					for (int i = 0; i < this.migrationAgentNames.size(); i++) {
						/*
						 * Request Migration
						 */
						Event reqMigration = new Event((String)this.migrationAgentNames.elementAt(i),this.executionEngine.getExecutionEngineName(), Event.MGR_REQUEST,Event.NOW);

						reqMigration.setParam(ParamsLabel.EVT_ADDRESS, (String)this.migrationAddresses.elementAt(i));
						reqMigration.setParam(ParamsLabel.AGT_NAME, (String)this.migrationAgentNames.elementAt(i));
						this.executionEngine.getCommunicationChannel().send(reqMigration);
					}
					this.migrationAddresses = new Vector();
					this.migrationAgentNames = new Vector();
				}
			} catch (Exception e) {		
				LedsManager.error(LEDColor.WHITE);
				e.printStackTrace();
			}
		} // while
	}

	/**
	 * This method waits for agents to migrate
	 */
	private synchronized void  waitForAgentsToMigrate() {
		
		while (this.migrationAgentNames.size() == 0 && !this.inMigration) {
			try {
				wait();
			}
			catch (InterruptedException e) {
				LedsManager.error(LEDColor.WHITE);
				e.printStackTrace();
			}
		}
	}
	

}

